/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.jarpackager; import java.util.jar.*; import java.util.*; import java.io.*; import java.text.MessageFormat; import org.openide.filesystems.*; import org.openide.loaders.DataObject; import org.openide.loaders.MultiDataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.util.enum.RemoveDuplicatesEnumeration; import org.openide.util.NbBundle; import org.netbeans.modules.jarpackager.util.VersionSerializator; import org.netbeans.modules.jarpackager.options.JarPackagerOption; /** This class is responsible for holding all the information needed * to create an archive. * Archive is fully specified and described by a set of properties * which this class defines. * Class is serializable to allow clients to store the information * about archive for later refreshing of the content of archive.<p> * Jar package is created from a set of file objects. * Clients will use putFile method to create set of root file objects * which will represent the content of resulting jar. If the root file * object is folder, then all its content will be added into jar. * Clients can also specify FileObjectFilter implementation to control * which types of files to put in the resulting jar.<p> * * Manifest handling is divided into two approaches: * 1. It's possible to specify main manifest attributes in the contructor.<br> * 2. By specifying AttributesProducer implementation, client can * add per-entry manifest attributes for specific file objects.<br> * * Clients can also specify extra information through callback interface * ExtraInfoProducer. * * @author Dafe Simonek */ public class JarContent extends Object { /** Accepts class files only */ public static final FileObjectFilter CLASSES_ONLY = new ClassesOnlyFilter(); /** Accepts all but java files */ public static final FileObjectFilter DEFAULT = new AllButJavaFilter(); /** Accepts all files */ public static final FileObjectFilter ALL = new AcceptAllFilter(); /** Set of file objects to represent the content of * the jar file */ ArrayList content; /** The filter for file objects */ FileObjectFilter filter; /** Manifest instance */ Manifest manifest; /** Mapping between names and contents of extra info entries */ ExtraInfoProducer extraProducer; /** Flag for automatic file list creation in manifest */ boolean manifestFileList; /** Flag for automatic main manifest attributes generation */ boolean mainAttributes; /** true if jar should be compressed, false otherwise */ boolean compressed; /** level of the compression */ int compressionLevel; /** target file containing jar */ File targetFile; /** manager for versioned serialization */ VersionSerializator serializationManager; /** Creates new JarContent with default file object filter which * accepts all but java files and with no manifest. */ public JarContent () { this(DEFAULT); } /** Creates new JarContent with given main manifest attributes and * with given file object filter. * @param filter File object filter */ public JarContent (FileObjectFilter filter) { this.filter = filter; manifestFileList = false; compressed = true; compressionLevel = 6; mainAttributes = true; } /** Adds specified file object to the jar content. * When a folder is specified, all its subentries will * be inserted into the archive too during the process of * creating archive.<br> * Duplicates are not allowed (method checks it automatically) */ public synchronized void putFile (FileObject file) { List content = getContent(); if (!content.contains(file)) { content.add(file); } } /** Inserts specified array of file objects into the * jar content. */ public synchronized void putFiles (FileObject[] files) { for (int i = 0; i < files.length; i++) { putFile(files[i]); } } /** Inserts specified collection of file objects into the * jar content. */ public synchronized void putFiles (Collection files) { for (Iterator iter = files.iterator(); iter.hasNext(); ) { putFile((FileObject)iter.next()); } } /** Removes specified file object from the archive content */ public synchronized void removeFile (FileObject file) { getContent().remove(file); } /** Removes specified array of file objects from the archive content */ public synchronized void removeFiles (FileObject[] files) { for (int i = 0; i < files.length; i++) { removeFile(files[i]); } } /** @return list of root file objects which was added by putFiles(...) * methods */ public List getRoots () { return getContent(); } /** Removes all file objects that were previously added by * calling putFile(..) and putFiles(..) methods */ public synchronized void clear () { getContent().clear(); } /** Returns currently asociated file object filter. */ public FileObjectFilter getFilter () { return filter; } /** Sets new filter for filtering file objects in archive. * @filter New filter */ public synchronized void setFilter (FileObjectFilter filter) { this.filter = filter; } /** Returns currently asociated extra info producer. * (or null if no asociated) */ public ExtraInfoProducer getExtraProducer () { return extraProducer; } /** Sets new producer for attaching extra information to the archive. * @filter New extra info producer (can be null) */ public synchronized void setExtraProducer (ExtraInfoProducer extraProducer) { this.extraProducer = extraProducer; } /** Sets the manifest of the archive. */ public void setManifest (Manifest manifest) { this.manifest = manifest; } /** @return the manifest of archive described by this jar content. * Completion of the manifest is performed first if needed. * * Note that when you modify the manifest returned from this method, * you will also update manifest of this jar content. It is not really * needed to call setManifest again to re-store modified manifest. */ public Manifest getManifest () { return manifest; } /** Enables or disables automatic generation of archive file * list to the manifest */ public void setManifestFileList (boolean manifestFileList) { this.manifestFileList = manifestFileList; } /** @return true if automatic generation of archive file list * in manifest is enabled, false otherwise. Default is false. */ public boolean isManifestFileList () { return manifestFileList; } /** Enables or disables automatic generation of various * main attributes of the manifest */ public void setMainAttributes (boolean mainAttributes) { this.mainAttributes = mainAttributes; } /** @return true if automatic generation of main attributes in * enabled, false otherwise. Enabled by default. */ public boolean isMainAttributes () { return mainAttributes; } /** @return target file which contains jar * (or will contain jar if it is not created yet) */ public File getTargetFile () { return targetFile; } /** Sets new target file. * @param targetFile new target file */ public void setTargetFile (File targetFile) { this.targetFile = targetFile; } /** Getter for property compress. *@return Value of property compress. */ public boolean isCompressed () { return compressed; } /** Setter for property compress. *@param compress New value of property compress. */ public void setCompressed (boolean compressed) { this.compressed = compressed; } /** @return current compression level (0-9). Property is * ignored when compression is disabled */ public int getCompressionLevel () { return compressionLevel; } /** Sets new compression level (0-9). Ignored when * compression is disabled. Throws IllegalArgumentException * if new compression level is invalid. */ public void setCompressionLevel (int compressionLevel) { if ((compressionLevel < 0) || (compressionLevel > 9)) throw new IllegalArgumentException( MessageFormat.format( NbBundle.getBundle(JarContent.class).getString("FMT_InvalidLevel"), new Object[] { new Integer(compressionLevel) } ) ); this.compressionLevel = compressionLevel; } /** Returns the list with all file objects (not only that were added by * putFile(...) methods, but also with all their subentries - recursively * went through) that are currently selected to be in resulting * jar archive and satisfies file object filter conditions. * @result list which holds all filtered entries of current content */ public synchronized List filteredContent () { // get the enumeration of the content Enumeration enum = new RemoveDuplicatesEnumeration(new AllFiles(getContent())); // filter the enumeration and convert into resulting list ArrayList result = new ArrayList(); FileObject curFo = null; while (enum.hasMoreElements()) { curFo = (FileObject)enum.nextElement(); if (filter.accept(curFo)) { // create named entry result.add(curFo); } } return result; } /** Returns full content (recursively went through) of the archive * as enumeration. Performs no filtering. * @return Enumeration of all entries of current content. */ public synchronized Enumeration fullContent () { // get the enumeration of the content return new RemoveDuplicatesEnumeration(new AllFiles(getContent())); } /** Safe accessor for content */ public List getContent () { if (content == null) { content = new ArrayList(); } return content; } // only for testing /*public String toString () { return "Content: " + getContent().toString() + "\n" + "Filter: " + filter + "\n" + "Extra producer: " + extraProducer; }*/ /** Versioned deserialization */ public void readContent (ObjectInput in) throws IOException, ClassNotFoundException { VersionSerializator vs = serializationManager(); vs.readVersion(in); } /** Versioned serialization */ public void writeContent (ObjectOutput out) throws IOException { VersionSerializator vs = serializationManager(); vs.writeLastVersion(out); } /** Safe getter for serialization manager which * manages versioned serisalization */ private VersionSerializator serializationManager () { if (serializationManager == null) { serializationManager = new VersionSerializator(); serializationManager.putVersion(new Version1Serializator(this)); } return serializationManager; } /** Enumeration that enumerates all file objects from the * given set of root file objects. */ static final class AllFiles implements Enumeration { /** Array of : * 1) file objects (for primary entries of data objects) * 2) iterators (for seconmdary entries of data objects) * 3) enumerations (for folders) */ ArrayList roots; /** Enumeration of children of currently processed * root file object or iterator of secondary entries of processed * item */ Object curEnum; /** Currently processed item in array */ int curIndex = 0; AllFiles (List content) { FileObject[] fos = (FileObject[])content.toArray(new FileObject[0]); roots = new ArrayList(fos.length*2); DataObject curDo = null; for (int i = 0; i < fos.length; i++) { try { curDo = DataObject.find(fos[i]); } catch (DataObjectNotFoundException exc) { // ignore and continue to next file object // PENDING - warning to the output window ? exc.printStackTrace(); continue; } // System.out.println(fos[i].getName()); if (fos[i].isFolder()) { // folder (add all its children) roots.add(fos[i].getData(true)); } else { // not folder - add all its files // PENDING - ensure that children are initialized approprietly roots.add(curDo.files().iterator()); } } curIndex = 0; } /** Tests if this enumeration contains more elements. */ public boolean hasMoreElements () { // ask current enumeration of iterator if (doHasNext(curEnum)) { return true; } else { curEnum = null; } // try to inspect following items to find out if we are finished Object curItem = null; for (int i = curIndex; i < roots.size(); i++) { curItem = roots.get(i); if ((curItem instanceof FileObject) || doHasNext(curItem)) { return true; } } // yep, we are on the end, no next elements exist return false; } /** Returns the next element of this enumeration if this * enumeration object has at least one more element to provide. */ public Object nextElement () throws NoSuchElementException { // return next element from current enumeration or iterator // if this is possible if (doHasNext(curEnum)) { return doNext(curEnum); } else { curEnum = null; } // continue further and search next item Object curItem = null; for (int i = curIndex; i < roots.size(); i++) { curItem = roots.get(i); if (curItem instanceof FileObject) { curIndex = i + 1; return curItem; } if (doHasNext(curItem)) { curIndex = i + 1; curEnum = curItem; return doNext(curItem); } } // no next element exists throw new NoSuchElementException(); } /** @return true if given iterator or enumeration has * next elements, false otherwise */ private boolean doHasNext (Object enumOrIter) { return ((enumOrIter instanceof Enumeration) && ((Enumeration)enumOrIter).hasMoreElements()) || ((enumOrIter instanceof Iterator) && ((Iterator)enumOrIter).hasNext()); } /** @return next element of given enumeration or iterator. * Throws NoSuchElementException if no next element exist. */ private Object doNext (Object enumOrIter) throws NoSuchElementException { if (enumOrIter instanceof Enumeration) { return ((Enumeration)enumOrIter).nextElement(); } if (enumOrIter instanceof Iterator) { return ((Iterator)enumOrIter).next(); } throw new NoSuchElementException(); } } // end of AllFiles inner class /** Implementation of the filter that accepts only *.class files */ private static final class ClassesOnlyFilter implements FileObjectFilter { static final long serialVersionUID = 7475557013758392767L; public boolean accept (FileObject fo) { return "class".equals(fo.getExt()); // NOI18N } /** Keep the uniquennes of this filter after deserialization */ private Object readResolve () throws ObjectStreamException { return CLASSES_ONLY; } }; /** Implementation of the filter that accepts all but *.java, *.form files */ private static final class AllButJavaFilter implements FileObjectFilter { static final long serialVersionUID = -6474655716324211768L; public boolean accept (FileObject fo) { String extension = fo.getExt(); return !("java".equals(extension)) && !("jar".equals(extension)) && // NOI18N !("form".equals(extension)) && // NOI18N (JarPackagerOption.singleton().getContentExt() != extension); } /** Keep the uniquennes of this filter after deserialization */ private Object readResolve () throws ObjectStreamException { return DEFAULT; } }; /** Implementation of the filter that accepts all files */ private static final class AcceptAllFilter implements FileObjectFilter { static final long serialVersionUID = 8921981094756492767L; public boolean accept (FileObject fo) { return true; } /** Keep the uniquennes of this filter after deserialization */ private Object readResolve () throws ObjectStreamException { return ALL; } }; /** This class takes care of serializaton of JarContent * in first version of jar packager module. */ static final class Version1Serializator implements VersionSerializator.Versionable { /** Asociated jar content to manage serialization for */ JarContent jc; public Version1Serializator (JarContent jc) { this.jc = jc; } public String getName () { return "Version_1.0"; // NOI18N } public void readData (ObjectInput in) throws IOException, ClassNotFoundException { jc.content = (ArrayList)in.readObject(); jc.filter = (FileObjectFilter)in.readObject(); jc.extraProducer = (ExtraInfoProducer)in.readObject(); jc.targetFile = (File)in.readObject(); jc.manifestFileList = ((Boolean)in.readObject()).booleanValue(); jc.mainAttributes = ((Boolean)in.readObject()).booleanValue(); jc.compressed = ((Boolean)in.readObject()).booleanValue(); jc.compressionLevel = ((Integer)in.readObject()).intValue(); if (((Boolean)in.readObject()).booleanValue()) { jc.manifest = new Manifest(); jc.manifest.read((InputStream)in); } } public void writeData (ObjectOutput out) throws IOException { out.writeObject(jc.content); out.writeObject(jc.filter); out.writeObject(jc.extraProducer); out.writeObject(jc.targetFile); out.writeObject(new Boolean(jc.manifestFileList)); out.writeObject(new Boolean(jc.mainAttributes)); out.writeObject(new Boolean(jc.compressed)); out.writeObject(new Integer(jc.compressionLevel)); out.writeObject(new Boolean(jc.manifest != null)); if (jc.manifest != null) jc.manifest.write((OutputStream)out); } } // end of Version1Serializator inner class } /* * <<Log>> * 24 Gandalf-post-FCS1.22.1.0 3/15/00 David Simonek serialization fix * (serialization UIDs added) * 23 Gandalf 1.22 1/25/00 David Simonek Various bugfixes and i18n * 22 Gandalf 1.21 1/16/00 David Simonek i18n * 21 Gandalf 1.20 11/10/99 David Simonek Workaround for bug - * MultiDataObject secondary entries incorrectly empty (caused missing * files in archive) * 20 Gandalf 1.19 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 19 Gandalf 1.18 10/13/99 David Simonek various bugfixes * concerning primarily manifest * 18 Gandalf 1.17 10/10/99 Petr Hamernik console debug messages * removed. * 17 Gandalf 1.16 10/5/99 David Simonek various fixes, only * primary entries now resides in 'chosen content' * 16 Gandalf 1.15 10/4/99 David Simonek * 15 Gandalf 1.14 9/16/99 David Simonek a lot of bugfixes (RE * filters, empty jar content etc) added templates * 14 Gandalf 1.13 9/13/99 David Simonek bugfixes, compressed * on/off support fixed * 13 Gandalf 1.12 9/8/99 David Simonek new version of jar * packager * 12 Gandalf 1.11 8/1/99 David Simonek another bugfixes... * 11 Gandalf 1.10 8/1/99 David Simonek automatic file list * generation to the manifest added * 10 Gandalf 1.9 6/10/99 David Simonek progress indocator + * minor bugfixes.... * 9 Gandalf 1.8 6/9/99 David Simonek bugfixes, progress * dialog, compiling progress.. * 8 Gandalf 1.7 6/9/99 Ian Formanek ---- Package Change To * org.openide ---- * 7 Gandalf 1.6 6/8/99 David Simonek * 6 Gandalf 1.5 6/8/99 David Simonek bugfixes.... * 5 Gandalf 1.4 6/4/99 Petr Hamernik defe's bugfix * 4 Gandalf 1.3 6/4/99 David Simonek * 3 Gandalf 1.2 6/4/99 Petr Hamernik temporary version * 2 Gandalf 1.1 6/4/99 David Simonek manifest ceration now * correctly supported * 1 Gandalf 1.0 6/3/99 David Simonek * $ */